/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package intopo.evogame.partnerselection.config;

import intopo.evogame.partnerselection.player.PlainPlayer;
import intopo.evogame.partnerselection.population.GeographicPopulation.Geometry;
import intopo.game.DescriptionGUI;
import intopo.game.Game;
import intopo.game.Strategy;
import java.io.PrintStream;
import java.util.Scanner;


/**
 * A text configuration file that contains data to perform a simulation of the evolution of partner selection with plain players.
 *
 * <p> After reading a text file, the fields of this class contain the game and the population that are going to be used by the evolutionary algorithm.  Also global parameters
 * that affect the behaviour of chromosomes and plain players have new values, read from the file.
 *
 * <p> The format of the text file is composed of three main sections.  The first section contains the global parameters values.  The second section contains the game parameters.
 * The third and last section contain the population data.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0
 *
 * @see intopo.evogame.partnerselection.player.PlainPlayer
 *
 * @param <G> Type parameter that represents the game that is going to be used in the evoluationary algorithm.
 * @param <S> Type parameter that represents the game strategy that is going to be used by players in the evolutionary algorithm.
 */
final public class TextConfigFilePlainPlayersEditor<
		G extends Game<S>,
		S extends Strategy<S>>
	extends AbstractTextConfigFilePlainPlayers<DescriptionGUI<G, S>, G, S>
{
	private GeographicPopulationParameters population;

	/**
	 * Construct a text configuration file that uses the given game description to read game and strategy data.
	 *
	 * @param description
	 */
	public TextConfigFilePlainPlayersEditor (DescriptionGUI<G, S> description)
	{
		super (description);
		this.population = new GeographicPopulationParameters ();
	}

	public void setGame (G game)
	{
		this.game = game;
	}
	@Override
	public DescriptionGUI<G, S> getDescription ()
	{
		return this.description;
	}
	public void setDescription (DescriptionGUI<G, S> description)
	{
		this.description = description;
		this.population.descriptionChanged ();
	}
	/**
	 * Get the population that is represented by this configuration file.
	 * @return
	 */
	public GeographicPopulationParameters getPopulation ()
	{
		return this.population;
	}
	@Override
	protected void readPopulation (Scanner scanner)
		throws
			ParsingException,
			ConstructStrategyException,
			TagException
	{
		this.population.read (scanner);
	}

	@Override
	protected void writePopulation (PrintStream ps)
	{
		this.population.write (ps);
	}
	/**
	 * Parameters of a population that is located in a geographic landscape.  This class contains parameters that are not represented by any field of a class in package {@code intopo.evogame.partnerselection.population}.
	 *
	 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
	 *
	 * @version 1.0 4/Dez/2011
	 *
	 * @see intopo.evogame.partnerselection.population
	 */
	public class GeographicPopulationParameters
	{
		/**
		 * Width of the landscape in nodes.
		 */
		private int width;
		/**
		 * Height of the landscape in nodes.
		 */
		private int height;
		/**
		 * Parameters of each node in the landscape.
		 */
		private Object[][] node;
		/**
		 * Landscape geometry.  Specifies the neighbourhood type.
		 */
		private Geometry geometry;
		/**
		 * If {@code true} the landscape has boundaries.
		 */
		private boolean boundary;
		/**
		 * Construct a geographic population with 10 by 10 nodes.
		 */
		private GeographicPopulationParameters ()
		{
			this.width = 10;
			this.height = 10;
			this.geometry = Geometry.MOORE;
			this.boundary = false;
			this.node = new Object [this.width][this.height];
			for (int i = this.width - 1; i >= 0; i--) {
				for (int j = this.height - 1; j >= 0; j--) {
//					this.node [i][j] = new NodeParameters (i * 4 + j * 4 + 2);
					this.node [i][j] = new NodeParameters (10);
				}
			}
		}
		/**
		 * Get the node parameters at the given coordinates.
		 *
		 * @param x horizontal coordinate of the returned node.
		 * @param y vertical coordinate of the returned node.
		 *
		 * @return the node parameters at the given coordinates.
		 */
		public NodeParameters getNode (int x, int y)
		{
			return (NodeParameters) this.node [x][y];
		}
		/**
		 * Get the width of the landscape.
		 *
		 * @return the width of the landscape.
		 */
		public int getWidth ()
		{
			return this.width;
		}
		/**
		 * Change the width of the landscape.
		 *
		 * @param value New value of the landscape width.
		 *
		 * @throws java.lang.IllegalArgumentException  If the new value is less than 1
		 */
		public void setWidth (int value)
				  throws IllegalArgumentException
		{
			if (value < 1) {
				throw new IllegalArgumentException ("Width must be equal to or greater than 1");
			}
			if (value > this.node.length) {
				this.resizeNode (value, this.node [0].length);
			}
			this.width = value;
		}
		/**
		 * Get the landscape height.
		 *
		 * @return the landscape height.
		 */
		public int getHeight ()
		{
			return this.height;
		}
		/**
		 * Change the landscape height.
		 * @param value New value of landscape height.
		 * @throws java.lang.IllegalArgumentException If the new value is less than 1.
		 */
		public void setHeight (int value)
			throws IllegalArgumentException
		{
			if (value < 1) {
				throw new IllegalArgumentException ("Height must be equal to or greater than 1");
			}
			if (value > this.node [0].length) {
				this.resizeNode (this.node.length, value);
			}
			this.height = value;
		}
		/**
		 * Invoked when the description.  We remove any references to strategies in the population.
		 */
		void descriptionChanged ()
		{
			for (int i = this.width - 1; i >= 0; i--) {
				for (int j = this.height - 1; j >= 0; j--) {
					this.getNode (i, j).descriptionChanged ();
				}
			}
		}

		public Geometry getGeometry ()
		{
			return this.geometry;
		}
		public void setGeometry (Geometry geometry)
		{
			this.geometry = geometry;
		}

		/**
		 * Returns {@code true} if the landscape is bounded or if overlaps as a torus.
		 *
		 * @return {@code true} if the landscape is bounded.
		 */
		public boolean isBounded ()
		{
			return this.boundary;
		}
		/**
		 * Changes the boundary condition to the given value.
		 * @param value
		 */
		public void setBounded (boolean value)
		{
			this.boundary = value;
		}
		void read (Scanner scanner)
			throws
				ParsingException,
				ConstructStrategyException,
				TagException
		{
			this.width = readInt (scanner, "environment width");
			this.height = readInt (scanner, "environment height");
			int ordinal = readInt (scanner, "environment geometry");
			this.geometry = Geometry.values () [ordinal];
			this.boundary = readInt (scanner, "boundary") == 1;
			this.node = new Object[this.width][this.height];
			for (int x = 0; x < width; x++) {
				for (int y = 0; y < height; y++) {
					this.node [x][y] = new NodeParameters ();
					this.getNode (x, y).read (scanner);
				}
			}
		}

		void write (PrintStream ps)
		{
			ps.print (this.getWidth ());
			ps.println ("\t# environment width");
			ps.print (this.getHeight ());
			ps.println ("\t# environment height");
			ps.print (this.geometry.ordinal ());
			ps.println ("\t# environment geometry");
			ps.print (this.boundary ? 1 : 0);
			ps.println ("\t# boundary");
			for (int x = 0; x < width; x++) {
				for (int y = 0; y < height; y++) {
					ps.println ();
					this.getNode (x, y).write (ps);
				}
			}
		}
		private void resizeNode (int w, int h)
		{
			Object[][] copy = new Object [w][h];
			for (int x = this.width - 1; x >= 0; x--) {
				//    (0,0)------(width,0)------(w,0)
				//      :    ###     :            :
				//      :    ###     :            :
				// (0,height)------      ----(w,height)
				//      :            :            :
				//      :            :            :
				//    (0,h)--------      -------(w,h)
				for (int y = this.height - 1; y >= 0; y--) {
					copy [x][y] = this.node [x][y];
				}
				//    (0,0)------(width,0)------(w,0)
				//      :            :            :
				//      :            :            :
				// (0,height)------      ----(w,height)
				//      :    ###     :            :
				//      :    ###     :            :
				//    (0,h)--------      -------(w,h)
				for (int y = this.height; y < h; y++) {
					copy [x][y] = new NodeParameters ();
				}

			}
			//    (0,0)------(width,0)------(w,0)
			//      :            :    ###     :
			//      :            :    ###     :
			// (0,height)------       ###(w,height)
			//      :            :    ###     :
			//      :            :    ###     :
			//    (0,h)--------      -------(w,h)
			for (int x = this.width; x < w; x++) {
				for (int y = h - 1; y >= 0; y--) {
					copy [x][y] = new NodeParameters ();
				}
			}
			this.node = copy;
		}
		@Override
		public String toString ()
		{
			return String.format ("%dx%d", width, height);
		}
	}

	/**
	 * This class contains configuration parameters that are not represent by any field of in class {@code intopo.evogame.partnerselection.population.Node}.
	 * <p> The migration probability is a global parameter of a geographic population and is represented by field {@code Node.Parameters.migrationProbability}.
	 * <p> A node has a carrying capacity and a set of players.  Each player is represented by its chromosome and strategy.
	 *
	 * @see intopo.evogame.partnerselection.population.Node.Parameters#migrationProbability
	 *
	 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
	 *
	 * @version 1.0 8/Dez/2011
	 */
	public class NodeParameters
		implements Cloneable
	{
		/**
		 * Carrying capacity of the node.
		 */
		private int carryingCapacity;
		/**
		 * The players that are in this node.
		 */
		final private java.util.Vector<Player> players;
		/**
		 * Construct a node with default carrying capacity of 10.
		 */
		private NodeParameters ()
		{
			carryingCapacity = 10;
			this.players = new java.util.Vector<Player> ();
		}
		/**
		 * Construct a node parameters with the given data.
		 * @param carryingCapacity Value of the carrying capacity.
		 * @throws IllegalArgumentException if the carrying capacity is equal or less than 1.
		 */
		private NodeParameters (int carryingCapacity)
			throws IllegalArgumentException
		{
			this.setCarryingCapacity (carryingCapacity);
			this.players = new java.util.Vector<Player> ();
		}
		@Override
		public NodeParameters clone ()
		{
			NodeParameters result = new NodeParameters ();
			result.carryingCapacity = this.carryingCapacity;
			for (Player p : this.players) {
				result.players.add (p.clone ());
			}
			return result;
		}
		/**
		 * Clears the node.  The carrying capacity is reset to the lowest value and the players are removed from this node.
		 */
		public void clear ()
		{
			this.carryingCapacity = 2;
			this.players.clear ();
		}
		/**
		 * Get the carrying capacity.
		 * @return the carrying capacity.
		 */
		public int getCarryingCapacity ()
		{
			return this.carryingCapacity;
		}
		/**
		 * Sets the carrying capacity of this node.
		 * @param value New value of the carrying capacity.
		 * @throws IllegalArgumentException if the new value is equal or less than 1.
		 */
		public void setCarryingCapacity (int value)
			throws IllegalArgumentException
		{
			if (value <= 1) {
				throw new java.lang.IllegalArgumentException ("Carrying capacity must be greater than 1");
			}
			this.carryingCapacity = value;
		}
		public Player getPlayer (int index)
		{
			return this.players.get (index);
	//		java.util.Iterator<java.util.Map.Entry<Player, Integer>> iterator;
	//		iterator = this.players.entrySet ().iterator ();
	//		while (index > 0) {
	//			iterator.next ();
	//			index++;
	//		}
	//		return iterator.next ();
		}
		public int getNumberPlayers ()
		{
			return this.players.size ();
		}
		public void addPlayer (Player player)
		{
			this.players.add (player.clone ());
		}
		/**
		 * Add a player with the given strategy.  This method is called when the user clicks the add button.
		 * @param value
		 */
		public void addPlayer (S value)
		{
			this.players.add (new Player (value));
		}
		public void removePlayer (int index)
		{
			this.players.remove (index);
		}
		/**
		 * Get the number of individuals that are in this node.  It is the sum of all player quantities, rather than the number of {@code Player} instances.
		 *
		 * @see Player#quantity
		 * @see #getNumberPlayers()
		 *
		 * @return the number of individuals that are in this node.
		 */
		public int getNumberIndividuals ()
		{
			int result = 0;
			for (Player p : this.players) {
				result += p.quantity;
			}
			return result;
		}
		/**
		 * When the game description changed, we remove the players in this node.
		 */
		private void descriptionChanged ()
		{
			this.players.clear ();
		}
		private void read (Scanner scanner)
			throws
				ConstructStrategyException,
				TagException,
				ParsingException
		{
			int size = AbstractTextConfigFile.readInt (scanner, "number of entries");
			this.players.clear ();
			while (size > 0) {
				Player p = new Player (null);
				p.read (scanner);
				this.players.add (p);
				size--;
			}
			this.carryingCapacity = AbstractTextConfigFile.readInt (scanner, "carrying capacity");
		}
		private void write (PrintStream ps)
		{
			ps.print (this.players.size ());
			ps.println ("\t# number of entries");
			for (Player p : this.players) {
				p.write (ps);
			}
			ps.print (this.carryingCapacity);
			ps.println ("\t# carrying capacity");
		}
		@Override
		public String toString ()
		{
			return String.format ("%d %s", this.carryingCapacity, this.players.toString ());
		}

	}



	/**
	 * Represents a collection of players with the same chromosome and strategy.
	 *
	 * <p> In the configuration file, the data about players in a node is stored in records.
	 * Each record contains three fields: the number of individuals, the chromosome of the individuals, and the strategy shared by the individuals.
	 * Each of these fields can be edited.  The individuals may use a default chromosome.  The strategy depends on the game.
	 */
	public class Player
		implements Cloneable
	{
		/**
		 * Number of individuals.
		 */
		private int quantity;
		/**
		 * The chromosome the individuals share.
		 */
		private Chromosome chromosome;
		/**
		 * The strategy shared by the individuals.
		 */
		private S strategy;
		/**
		 * Construct a new player record for some node.
		 * @param strategy
		 */
		private Player (S strategy)
		{
			this.quantity = 1;
			this.chromosome = new Chromosome ();
			this.strategy = strategy;
		}
		@Override
		public Player clone ()
		{
			Player result = new Player (this.strategy);
			result.quantity = this.quantity;
			result.chromosome = this.chromosome.clone ();
			return result;
		}
		public int getQuantity ()
		{
			return this.quantity;
		}
		public void setQuantity (int value)
		{
			if (value < 0) {
				throw new IllegalArgumentException ("Quantity must be greater than or equal to 0");
			}
			this.quantity = value;
		}

		public Chromosome getChromosome ()
		{
			return this.chromosome;
		}
		public void setChromosome (Chromosome value)
		{
			this.chromosome = value;
		}
		public S getStrategy ()
		{
			return this.strategy;
		}
		public void setStrategy (S value)
		{
			this.strategy = value;
		}
		private void read (Scanner scanner)
			throws
				ConstructStrategyException,
				TagException
		{
			this.quantity = scanner.nextInt ();
			if (!intopo.evogame.partnerselection.chromosome.Chromosome.Parameters.defaultGenes) {
				this.chromosome.read (scanner);
			}
			this.strategy = readStrategy (scanner);
		}
		private void write (PrintStream ps)
		{
			ps.print (this.quantity);
			ps.print ('\t');
			if (!intopo.evogame.partnerselection.chromosome.Chromosome.Parameters.defaultGenes) {
				this.chromosome.write (ps);
				ps.print ('\t');
			}
			this.strategy.print (ps);
			ps.println ();
		}
		@Override
		public String toString ()
		{
			return String.format ("#%d %s %s", this.quantity, this.chromosome, this.strategy);
		}
	}
	/**
	 * Chromosome parameters of a player in the configuration file.
	 */
	static public class Chromosome
		implements Cloneable
	{
		/**
		 * Size of probability and combination vectors.  This field represents
		 * parameter <i>l</i> mentioned in the paper.
		 */
		private int poolSize = 0;

		/**
		 * Utility threshold used in the decision to update the probability and
		 * combination vectors.  This field represents parameter
		 * <i>u<sub>t</sub></i> mentioned in the paper.
		 *
		 * <p><b>NOTE:</b> In this implementation, utility is equal to the
		 * payoff obtained by the player in the game.
		 */
		private float utilityThreshold;

		/**
		 * Multiplication factor used when a probability is decreased because
		 * the player obtained a utility lower than {@code utilityThreshold}.
		 * This field represents parameter <i>&delta;</i> mentioned in the
		 * paper.
		 */
		private float delta = 0;

		/**
		 * Number of bits used in encoding probabilities of the probability vector.  Valid values range from 1 to 8.
		 */
		private byte bitsPerProbability = 1;
		@Override
		public Chromosome clone ()
		{
			Chromosome result = new Chromosome ();
			result.bitsPerProbability = this.bitsPerProbability;
			result.delta = this.delta;
			result.poolSize = this.poolSize;
			result.utilityThreshold = this.utilityThreshold;
			return result;
		}
		public int getPoolSize ()
		{
			return this.poolSize;
		}
		/**
		 *
		 * @param value
		 * @throws java.lang.IllegalArgumentException
		 */
		public void setPoolSize (int value)
			throws IllegalArgumentException
		{
			if (value < 0) {
				throw new IllegalArgumentException ("Pool size must be greater than or equal to zero");
			}
			this.poolSize = value;
		}
		public float getUtilityThreshold ()
		{
			return this.utilityThreshold;
		}
		public void setUtilityThreshold (float value)
		{
			this.utilityThreshold = value;
		}
		public float getDelta ()
		{
			return this.delta;
		}
		public void setDelta (float value)
		{
			if (value < 0 || value > 1) {
				throw new IllegalArgumentException ("Delta must be within 0 and 1 inclusive");
			}
			this.delta = value;
		}
		public byte getBitsPerProbability ()
		{
			return this.bitsPerProbability;
		}
		public void setBitsPerProbability (byte value)
		{
			if (value < 1 || value > 8) {
				throw new IllegalArgumentException ("Bits per probability must be within 1 and 8 inclusive");
			}
			this.bitsPerProbability = value;
		}
		private void read (Scanner scanner)
		{
			this.poolSize = scanner.nextInt ();
			if (this.poolSize < 0) {
				throw new IllegalArgumentException ("Pool size must be greater than or equal to zero");
			}
			this.utilityThreshold = scanner.nextFloat ();
			this.delta = scanner.nextFloat ();
			if (this.delta < 0 || this.delta > 1) {
				throw new IllegalArgumentException ("Delta must be within 0 and 1 inclusive");
			}
			this.bitsPerProbability = scanner.nextByte ();
			if (this.bitsPerProbability < 1 || this.bitsPerProbability > 8) {
				throw new IllegalArgumentException ("Bits per probability must be within 1 and 8 inclusive");
			}
		}
		private void write (PrintStream ps)
		{
			ps.print (this.poolSize);
			ps.print (' ');
			ps.print (this.utilityThreshold);
			ps.print (' ');
			ps.print (this.delta);
			ps.print (' ');
			ps.print (this.bitsPerProbability);
		}
	}

}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
